<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>keyframe-animation，播放某一帧，实现交互动画</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
        /* 隐藏body窗口区域滚动条 */
      }
      #controlBox {
        position: fixed;
        top: 100px;
        left: 50%;
        transform: translateX(-50%);
      }
      #input {
        width: 1000px;
      }
    </style>
    <!--引入three.js三维引擎-->
    <!-- <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/build/three.js"></script> -->
    <!-- <script src="http://www.yanhuangxueyuan.com/threejs/build/three.js"></script> -->
    <!-- Three.js-master 包示例中的用法 examples/js/controls/OrbitControls.js  版本要匹配-->
    <script src="http://www.yanhuangxueyuan.com/3D/example/three.min.js"></script>
    <script src="../../util/OrbitControls.js"></script>
  </head>
  <body>
    <div id="controlBox">
      <input
        type="range"
        name="input"
        id="input"
        value="100"
        min="0"
        max="100"
      />
    </div>
    <script type="module">
      import {
        RAFTime,
        sphereMesh,
        cylinderMesh,
        v2ToV3,
      } from "../../util/index.js";

      /**
       * 创建场景对象Scene
       */
      var scene = new THREE.Scene();
      var group = new THREE.Group();
      // 创建一个时钟对象Clock
      var clock = new THREE.Clock();

      var boxGeometry = new THREE.BoxGeometry(60, 20, 20);
      var mesh1 = new THREE.Mesh(
        boxGeometry,
        new THREE.MeshStandardMaterial({
          color: 0xff000,
          side: THREE.DoubleSide,
          transparent: true,
          opacity: 0.9,
        })
      );
      mesh1.name = "Box";

      var sphereGeometry = new THREE.SphereGeometry(20, 100, 100);
      var mesh2 = new THREE.Mesh(
        sphereGeometry,
        new THREE.MeshStandardMaterial({
          color: 0xffff00,
          side: THREE.DoubleSide,
          transparent: true,
          opacity: 0.7,
        })
      );
      mesh2.name = "Sphere";

      group.add(mesh1);
      group.add(mesh2);

      // 编辑关键帧动画
      /**
       * 编辑group子对象网格模型mesh1和mesh2的帧动画数据
       */
      // 创建名为Box对象的关键帧数据
      var times = [0, 10]; //关键帧时间数组，离散的时间点序列
      var values = [0, 0, 0, 150, 0, 0]; //与时间点对应的值组成的数组
      // 创建位置关键帧对象：0时刻对应位置0, 0, 0   10时刻对应位置150, 0, 0
      var posTrack = new THREE.KeyframeTrack("Box.position", times, values);
      // 创建颜色关键帧对象：10时刻对应颜色1, 0, 0   20时刻对应颜色0, 0, 1
      var colorKF = new THREE.KeyframeTrack(
        "Box.material.color",
        [10, 20],
        [1, 0, 0, 0, 0, 1]
      );
      // 创建名为Sphere对象的关键帧数据  从0~20时间段，尺寸scale缩放3倍
      var scaleTrack = new THREE.KeyframeTrack(
        "Sphere.scale",
        [0, 20],
        [1, 1, 1, 3, 3, 3]
      );

      // duration决定了默认的播放时间，一般取所有帧动画的最大时间
      // duration偏小，帧动画数据无法播放完，偏大，播放完帧动画会继续空播放
      var duration = 20;
      // 多个帧动画作为元素创建一个剪辑clip对象，命名"default"，持续时间20
      var clip = new THREE.AnimationClip("default", duration, [
        posTrack,
        colorKF,
        scaleTrack,
      ]);

      /**
       * 播放编辑好的关键帧数据
       */
      // group作为混合器的参数，可以播放group中所有子对象的帧动画
      var mixer = new THREE.AnimationMixer(group);
      // 剪辑clip作为参数，通过混合器clipAction方法返回一个操作对象AnimationAction
      var AnimationAction = mixer.clipAction(clip);
      //通过操作Action设置播放方式
      AnimationAction.timeScale = 20; //默认1，可以调节播放速度
      AnimationAction.loop = THREE.LoopOnce; //不循环播放
      AnimationAction.clampWhenFinished = true; //暂停在最后一帧播放的状态
      AnimationAction.play(); //开始播放

      scene.add(group);

      /**
       * 辅助坐标系 参数250表示坐标系大小，可以根据场景大小去设置
       */
      var axisHelper = new THREE.AxisHelper(1000);
      scene.add(axisHelper);

      var loader = new THREE.CubeTextureLoader();
      // 所有贴图在同一目录下，可以使用该方法设置共用路径
      loader.setPath("../../public/images/cube/");

      // 平行光
      var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
      // 平行光辅助对象
      const directionalLightHelper = new THREE.DirectionalLightHelper(
        directionalLight,
        10
      );
      scene.add(directionalLightHelper);
      // 设置光源位置
      directionalLight.position.set(60, 60, 60);
      scene.add(directionalLight);
      // 设置用于计算阴影的光源对象
      directionalLight.castShadow = true;
      // 设置计算阴影的区域，最好刚好紧密包围在对象周围
      // 计算阴影的区域过大：模糊  过小：看不到或显示不完整
      directionalLight.shadow.camera.near = 0.5;
      directionalLight.shadow.camera.far = 300;
      directionalLight.shadow.camera.left = -100;
      directionalLight.shadow.camera.right = 100;
      directionalLight.shadow.camera.top = 200;
      directionalLight.shadow.camera.bottom = -200;
      // 设置mapSize属性可以使阴影更清晰，不那么模糊
      directionalLight.shadow.mapSize.set(1024, 1024);
      console.log(directionalLight.shadow.camera);

      //环境光
      var ambient = new THREE.AmbientLight(0x050505);
      scene.add(ambient);

      /**
       * 相机设置
       */
      var width = window.innerWidth; //窗口宽度
      var height = window.innerHeight; //窗口高度
      var k = width / height; //窗口宽高比
      // var s = 1; //三维场景显示范围控制系数，系数越大，显示的范围越大
      // //创建相机对象
      // var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 100);
      // ---------------------------------------目8.9 环境贴图，使用PerspectiveCamera比OrthographicCamera更真正，人眼视角
      var camera = new THREE.PerspectiveCamera(60, k, 1, 2000);
      camera.position.set(292, 109, 268); //设置相机位置
      camera.lookAt(scene.position); //设置相机方向(指向的场景对象)

      /**
       * 创建渲染器对象
       */
      var renderer = new THREE.WebGLRenderer();
      renderer.setSize(width, height); //设置渲染区域尺寸
      renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
      // 设置渲染器，允许光源阴影渲染
      renderer.shadowMap.enabled = true;
      document.body.appendChild(renderer.domElement); //body元素中插入canvas对象

      // // 监听鼠标、键盘事件，实现 旋转/缩放/平移
      // function render() {
      //   renderer.render(scene, camera); //执行渲染操作
      // }

      // 递归函数,每1/60秒左右,调用一次自己
      const render = RAFTime((t) => {
        //clock.getDelta()方法获得两帧的时间间隔
        // 更新混合器相关的时间
        mixer.update(clock.getDelta());
        renderer.render(scene, camera); //执行渲染操作
      });

      // 启动渲染---requestAnimationFrame周期性的渲染
      render();

      // 创建，轨迹控件---实现旋转/平移/缩放
      var controls = new THREE.OrbitControls(camera, renderer.domElement);
      // // 监听鼠标、键盘事件
      // controls.addEventListener("change", (e) => {
      //   console.log("e---", e);
      // });

      // 窗口大小变化后，设置画布 => 设置相机参数 => 更新投影矩阵
      window.onresize = function () {
        // 重置渲染器输出画布canvas尺寸
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 全屏情况下：设置观察范围长宽比aspect为窗口宽高比
        camera.aspect = window.innerWidth / window.innerHeight;
        // 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
        // 但是不会每渲染一帧，就通过相机的属性计算投影矩阵(节约计算资源)
        // 如果相机的一些属性发生了变化，需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
        camera.updateProjectionMatrix();
      };

      // 播放某一帧
      let inputDom = document.getElementById("input");
      inputDom.addEventListener("input", (e) => {
        console.log("e---", e.target.value);
        let currentTime = e.target.value / 5;
        AnimationAction.time = currentTime;
        clip.duration = currentTime;
      });
    </script>
  </body>
</html>
